/*
 * Copyright 2009-2010 Nanjing RedOrange ltd (http://www.red-orange.cn)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package redora.client.mvp;

import com.google.gwt.activity.shared.AbstractActivity;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.google.gwt.user.client.ui.Widget;
import redora.client.Persistable;
import redora.client.constants.RedoraMessages;
import redora.client.ui.ActiveWidget;
import redora.client.ui.Label;
import redora.client.ui.RedoraResource;
import redora.client.util.ClientUtil;
import redora.client.util.Field;
import redora.client.validation.BusinessRuleViolation;

import java.util.logging.Logger;

import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO;
import static redora.client.Persistable.Scope.Form;
import static redora.client.util.GWTViewUtil.getViolatedMessageLabelById;
import static redora.client.util.GWTViewUtil.getWidgetById;

/**
 * Edit Activity is looking at all the pojo's attributes and possibly changing them.
 * Further this activity also allows to create new pojos. Redora will generate a PojoEditView
 * for each pojo which extends {@link EditViewBase}.<br>
 * EditActivity is bind to {@link FetchEvent} to retrieve the pojo (by {@link redora.client.Service#findById(Long, redora.client.Persistable.Scope)}).
 * Also {@link PersistEvent} is bound: when the user clicks the save button, the activity will wait for this event.
 * If the event is onSuccess, it will return to the previous activity, if it was onViolation, it will
 * decorate the view with the {@link redora.client.validation.BusinessRuleViolation}s.
 *
 * @author Nanjing RedOrange (www.red-orange.cn)
 * @param <T>
 */
public class EditActivity<T extends Persistable> extends AbstractActivity implements EditView.Presenter, ActiveWidget {
    static Logger l = Logger.getLogger("EditActivity");

    final ClientFactory clientFactory;
    final Locator locator;
    EventBus eventBus;
    final EditView display;
    final ClientUtil<T> util;
    final Class<T> cls;
    Long fetchId;

    boolean allowStop = false;

    static RedoraResource resources = GWT.create(RedoraResource.class);
    static RedoraMessages messages = GWT.create(RedoraMessages.class);

    public EditActivity(Class<T> cls, ClientFactory clientFactory) {
        this.cls = cls;
        this.clientFactory = clientFactory;
        locator = clientFactory.getLocator();
        util = clientFactory.getLocator().locateUtil(cls);
        display = clientFactory.getLocator().locateEditView(cls);

        l.log(INFO, "Initialized EditActivity for " + cls);
    }

    @Override
    public void start(AcceptsOneWidget panel, EventBus eventBus) {
        this.eventBus = eventBus;
        display.initForm(); //Final set up of the widgets on this form
        panel.setWidget(display.asWidget());
        bind();
        Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {
            @Override
            public void execute() {
                load();
            }
        });
    }

    @Override
    public void load() {
        EditPlace here = (EditPlace) clientFactory.getPlaceController().getWhere();
        if (here.id != null) {
            fetchId = Long.valueOf(here.id);
            l.log(INFO, "Opening an edit " + cls + " form " + fetchId);
            clientFactory.getLocator().locateService(cls).findById(fetchId, Form);
            //Existing pojo requested from server or cache. When it arrives, the FetchEvents will pick it up
        } else {
            l.log(INFO, "Opening a new " + cls + " form");
            util.copy(null, display.panel(), display.fields()); //New pojo with default settings
        }
        display.startImage().setVisible(false);

        for (Field field : display.fields()) {
            if (getWidgetById(field, display.panel()) instanceof ActiveWidget) {
                l.log(FINE, "Loading widget for " + field.name);
                ((ActiveWidget) getWidgetById(field, display.panel())).load();
            }
        }

        l.log(INFO, "Finished starting EditActivity " + cls + ": " + fetchId);
    }

    private void bind() {
        display.saveButton().addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                T pojo = (T) clientFactory.getLocator().locateService(cls).cache(fetchId);
                util.copy(display.panel(), pojo, display.fields());
                if (!pojo.dirty().isEmpty()) {
                    l.log(INFO, "Save clicked for a dirty " + pojo);
                    clientFactory.getLocator().locateService(cls).persist(pojo);
                } else {
                    l.log(INFO, "Save clean pojo: no persist, returning to previous activity");
                    allowStop = true;
                    History.back();
                }
            }
        });
        display.cancelButton().addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                allowStop = true;
                History.back();
            }
        });
        eventBus.addHandler(FetchEvent.TYPE,
                new FetchEvent.Handler() {
                    @Override
                    public void onSuccess(FetchEvent event) {
                        if (event.hasMe(cls, fetchId)) {
                            l.log(INFO, "I will now decorate the panel with " + cls + ": " + fetchId);
                            util.copy((T) event.getPojo(fetchId), display.panel(), display.fields());
                        }
                    }
                });
        eventBus.addHandler(PersistEvent.TYPE,
                new PersistEvent.Handler() {
                    @Override
                    public void onSuccess(PersistEvent event) {
                        if (event.cls.equals(cls) && ((fetchId == null && event.wasNew) || (fetchId != null && fetchId.equals(event.pojo.getId())))) {
                            l.log(INFO, "Persist " + event.pojo + " was successful, i will return to the previous activity");
                            allowStop = true;
                            eventBus.fireEvent(new FetchEvent(cls, new Persistable[]{event.pojo}));
                            History.back(); //TODO this not always works.... change it
                        }
                    }

                    @Override
                    public void onViolation(PersistEvent event) {
                        if (event.cls.equals(cls) && ((fetchId == null && event.wasNew) || (fetchId != null && fetchId.equals(event.pojo.getId())))) {
                            l.log(INFO, "Showing business rule violation(s) in EditActivity " + cls + ": " + fetchId);
                            for (BusinessRuleViolation violation : event.violations) {
                                if (violation.field != null) {
                                    Widget widget = getWidgetById(violation.field, display.panel());
                                    if (widget != null)
                                        widget.getElement().addClassName(resources.css().redoraBRViolated());
                                    Label messageLabel = getViolatedMessageLabelById(violation.field, display.panel());
                                    messageLabel.setText(clientFactory.getLocator().locateMessage().rule(cls.getName(), violation.ruleId, null));
                                    messageLabel.setVisible(true);
                                }
                            }
                        }
                    }
                });
        l.log(INFO, "All handlers bind");
    }

    /**
     * Ask user before stopping this activity
     */
    @Override
    public String mayStop() {
        if (allowStop) {
            return null;
        }
        T pojo = (T)clientFactory.getLocator().locateService(cls).cache(fetchId);
        util.copy(display.panel(), pojo, display.fields());
        if (!pojo.dirty().isEmpty()) {
            return messages.unsavedChanges();
        }
        return null;
    }
}